package com.alan.camunda.boot.util;

import cn.hutool.core.util.IdUtil;
import org.camunda.bpm.model.bpmn.Bpmn;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.camunda.bpm.model.bpmn.instance.BaseElement;
import org.camunda.bpm.model.bpmn.instance.ExtensionElements;
import org.camunda.bpm.model.bpmn.instance.FlowElement;
import org.camunda.bpm.model.bpmn.instance.FlowNode;
import org.camunda.bpm.model.bpmn.instance.Message;
import org.camunda.bpm.model.bpmn.instance.MessageEventDefinition;
import org.camunda.bpm.model.bpmn.instance.MultiInstanceLoopCharacteristics;
import org.camunda.bpm.model.bpmn.instance.Process;
import org.camunda.bpm.model.bpmn.instance.ReceiveTask;
import org.camunda.bpm.model.bpmn.instance.SequenceFlow;
import org.camunda.bpm.model.bpmn.instance.StartEvent;
import org.camunda.bpm.model.bpmn.instance.TerminateEventDefinition;
import org.camunda.bpm.model.bpmn.instance.camunda.CamundaProperties;
import org.camunda.bpm.model.bpmn.instance.camunda.CamundaProperty;
import org.camunda.bpm.model.xml.instance.ModelElementInstance;
import org.checkerframework.checker.units.qual.A;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

public class BpmnUtil {
    private BpmnUtil() {
    }

    public static String getExtensionProperty(FlowElement fe, String propName) {
        ExtensionElements ees = fe.getExtensionElements();
        if (ees == null) {
            return null;
        }
        if (ees.getElementsQuery().filterByType(CamundaProperties.class).count() == 0) {
            return null;
        }
        CamundaProperties cps = ees.getElementsQuery().filterByType(CamundaProperties.class).singleResult();
        if (cps == null) {
            return null;
        }
        Collection<CamundaProperty> props = cps.getCamundaProperties();
        for (CamundaProperty cp : props) {
            if (cp.getCamundaName().equals(propName)) {
                return cp.getCamundaValue();
            }
        }
        return null;
    }

    public static Process getProcessNode(BpmnModelInstance mi) {
        Collection<Process> nodes = mi.getModelElementsByType(Process.class);
        return nodes.iterator().next();
    }


    /**
     * 获得节点的所有扩展属性，以 Map 的形式返回
     * @param be
     * @return
     */
    public static Map<String, String> getAllExtensionProperties(BaseElement be) {
        Map<String, String> result = new HashMap<>();
        ExtensionElements ees = be.getExtensionElements();
        if (ees == null) {
            return result;
        }
        CamundaProperties cps = ees.getElementsQuery().filterByType(CamundaProperties.class).singleResult();
        if (cps == null) {
            return result;
        }
        Collection<CamundaProperty> props = cps.getCamundaProperties();
        for (CamundaProperty cp : props) {
            result.put(cp.getCamundaName(), cp.getCamundaValue());
        }
        return result;
    }

    public static void adjustFlowIdAndFlowName(BpmnModelInstance mi, String flowId, String flowName) {
        Collection<Process> nodes = mi.getModelElementsByType(Process.class);
        Process process = nodes.iterator().next();

        process.setId(flowId);
        process.setName(flowName);
    }

    public static void adjustMessageNode(BpmnModelInstance mi) {
        Collection<Message> messages = mi.getModelElementsByType(Message.class);
        Collection<MessageEventDefinition> messageDefs = mi.getModelElementsByType(MessageEventDefinition.class);
        Collection<ReceiveTask> receiveTasks = mi.getModelElementsByType(ReceiveTask.class);
        System.out.println(messages);
        System.out.println(messageDefs);
        System.out.println(receiveTasks);

        for (Message message : messages) {
            String id = generateMessageId();
            message.setId(id);
            message.setName(id);
        }
    }


    public static String getCompletionCondition(BpmnModelInstance mi, String elementId) {
        ModelElementInstance modelElementInstance = mi.getModelElementById(elementId);
        Iterator<MultiInstanceLoopCharacteristics> iterator = modelElementInstance
               .getChildElementsByType(MultiInstanceLoopCharacteristics.class).iterator();
        if (iterator.hasNext()) {
            MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = iterator.next();
            return multiInstanceLoopCharacteristics.getCompletionCondition().getTextContent();
        }
        return null;
    }

    public static boolean isTopLevelTerminateEndNode(BpmnModelInstance mi, String elementId) {
        ModelElementInstance mei = mi.getModelElementById(elementId);
        if (mei == null) {
            return false;
        }
        if (!"endEvent".equals(mei.getElementType().getTypeName())) {
            return false;
        }
        if (!"process".equals(mei.getParentElement().getElementType().getTypeName())) {
            return false;
        }
        // 是否含有子元素 "terminateEventDefinition"
        Collection<TerminateEventDefinition> childs = mei.getChildElementsByType(TerminateEventDefinition.class);
        return childs.size() > 0;
    }

    private static String generateMessageId() {
        return "m_" + IdUtil.fastSimpleUUID().substring(0,10);
    }

    public static String toXml(BpmnModelInstance mi) {
        OutputStream outputStream = new ByteArrayOutputStream();
        // 将模型写入输出流
        Bpmn.writeModelToStream(outputStream, mi);
        // 将输出流转换为字符串
        return outputStream.toString();
    }

    /**
     * 获取所有与发起节点有效连接的节点
     * @return
     */
    public static List<FlowNode> getAllInvolvedNodes(BpmnModelInstance bmi) {
        Collection<StartEvent> startEvents = bmi.getModelElementsByType(StartEvent.class);
        // 如果启动节点没有后续的连线，则不是有效的启动节点
        if (startEvents.isEmpty()) {
            return new ArrayList<>();
        }
        Queue<FlowNode> candidateNodes = new LinkedList<>();
        List<FlowNode> result = new ArrayList<>();
        Set<String> involvedNodeIds = new HashSet<>();

        for (StartEvent startEvent : startEvents) {
            if (startEvent.getOutgoing().size() > 0) {
                // Start 节点有出线，则认为是有效的节点
                candidateNodes.add(startEvent);
                involvedNodeIds.add(startEvent.getId());
            }
        }

        while (!candidateNodes.isEmpty()) {
            FlowNode item = candidateNodes.poll();
            result.add(item);
            involvedNodeIds.add(item.getId());

            for (SequenceFlow sf : item.getOutgoing()) {
                FlowNode nextNode = sf.getTarget();
                if (!involvedNodeIds.contains(nextNode.getId())) {

                    candidateNodes.offer(nextNode);
                }
            }
        }
        //result.forEach(i -> System.out.println(i.getId()));
        return result;
    }

}
